package com.ivanceras.shared.db;

import java.util.Arrays;

import com.ivanceras.server.db.DBPool;
import com.ivanceras.shared.meta.ModelDefinitionProvider;
import com.ivanceras.shared.exception.DatabaseException;
import com.ivanceras.shared.exception.ModelDefinitionNotFoundException;


public class AsyncEntityManager {

	IDatabaseAsync db;
	/**
	 * If the entity is transaction based, get a database connection to (read/write) database
	 * else(read-only query) have to get a read-only database connection which would be faster.
	 */
	boolean isTransactionBased = false; 


	public AsyncEntityManager() throws DatabaseException{
		db = (IDatabaseAsync) DBPool.getDBPool().connectDB();
		if(db==null){
			throw new DatabaseException("Unable to create Database connection");
		}
	}
	
	public IDatabase getDB(){
		return db;
	}

	/**
	 * automatically search for the record whether to use 
	 * @param model
	 * @param dao
	 * @return
	 * @throws ClassNotFoundException 
	 * @throws InstantiationException 
	 * @throws IllegalAccessException 
	 * @throws  
	 * @throws Exception 
	 */
	public void getOne(DAO dao, final AsyncDBCallback<DAO> callback) throws DatabaseException{
		IModelDef modelDef = getModelDefinition(dao.getModelName());
		db.selectOne(modelDef, null, getFilters(dao), 0, 1, null, null, callback);
	}

	public void getTotalRecords(Class<? extends Object> daoClass, final AsyncDBCallback<Integer> callback) throws DatabaseException{
		getTotalRecords(daoClass.getSimpleName(), callback);
	}

	public void getTotalRecords(String daoClass,  final AsyncDBCallback<Integer> callback) throws DatabaseException{
		IModelDef modeldef= getModelDefinition(daoClass);
		db.getTotalRecords(modeldef.getNamespace(), modeldef.getModelName(), callback);
	}

	public void getOne(String daoClass, Filter[] filters,  final AsyncDBCallback<DAO> callback)  throws DatabaseException{
		IModelDef modelDef = getModelDefinition(daoClass);
		db.selectOne(modelDef, null, filters, null, null, null, null, callback);
	}
	public void getOne(DAO dao, Filter[] filters, final AsyncDBCallback<DAO> callback)  throws DatabaseException{
		IModelDef modelDef = getModelDefinition(dao.getModelName());
		db.selectOne(modelDef, null, filters, 0, 1, null, null, callback);
	}

	public void getAll(DAO dao, AsyncDBCallback<DAO[]> callback) throws DatabaseException{
		getAll(dao.getModelName(), null, null, null, null, null, callback);
	}
	
	public void getAll(DAO dao, Order[] orders, Filter[] filters, AsyncDBCallback<DAO[]> callback) throws DatabaseException{
		getAll(dao.getModelName(), orders, filters, null, null, null, callback);
	}

	public void getAll(DAO dao, Filter[] filters, Integer page, Integer itemsPerPage, Boolean doCount, Order[] orders, AsyncDBCallback<DAO[]> callback) throws DatabaseException{
		getAll(dao.getModelName(), orders, filters, page, itemsPerPage, doCount, callback);
	}
	public void getAll(String daoModelName, Order[] orders, Filter[] filters, Integer page, Integer itemsPerPage,  Boolean doCount, AsyncDBCallback<DAO[]> callback) throws DatabaseException{
		Integer offset = null;
		Integer limit = null;
		if(page != null && page <= 0){
			throw new DatabaseException("Page shoud be positive integer starting from 1");
		}
		else if(itemsPerPage != null){
			offset = (page -1) * itemsPerPage; 
			limit = itemsPerPage;
		}
		db.select(getModelDefinition(daoModelName), null, filters, offset, limit, doCount, orders, callback);

	}

	/**
	 * retrieves the children of this model
	 * @param dao
	 * @param model
	 * @param childModel
	 * @return
	 * @throws ClassNotFoundException 
	 * @throws Exception 
	 */
	/*
	public T[] getChildren(T dao, Class<? extends DAO> childClass, Order... order) throws DatabaseException{
		return getChildren(dao, childClass, order, null);
	}
	*/
	public void getChildren(DAO dao, DAO childDao, Order[] order, AsyncDBCallback<DAO[]> callback) throws DatabaseException{
		getChildren(dao, childDao, order, (Filter[])null, callback);
	}
	public void getChildren(DAO dao, DAO childDao, Order[] order, Filter[] filters, AsyncDBCallback<DAO[]> callback) throws DatabaseException{
		getChildren(dao, childDao, order, filters, null, null, null, callback);
	}
	public void getChildren(DAO dao, DAO childDao, Order[] order, Filter[] filters, Integer page, Integer itemPerPage, Boolean doCount, AsyncDBCallback<DAO[]> callback) throws DatabaseException{
		IModelDef childModelDef = getModelDefinition(childDao.getModelName());
		Filter[] defaultFilters = getForeignFilters(getModelDefinition(dao.getModelName()), dao, childModelDef);
		Integer offset = null;
		Integer limit = null;
		if(page != null && page <= 0){
			throw new DatabaseException("Page shoud be positive integer starting from 1");
		}
		else if(itemPerPage != null){
			offset = (page -1) * itemPerPage; 
			limit = itemPerPage;
		}
		db.select(childModelDef, null, mergeFilters(defaultFilters, filters), offset, limit, doCount, order, callback);
	}

	/**
	 * retrieves all the object that link this table
	 * @return
	 * @throws ClassNotFoundException 
	 * @throws Exception 
	 */
	public void getHasOne(DAO dao, DAO hasOneDao, AsyncDBCallback<DAO> callback) throws DatabaseException{
		getHasOne(dao, hasOneDao, null, callback);
	}

	public void getHasOne(DAO dao, DAO hasOneDao, Filter[] filters, AsyncDBCallback<DAO> callback) throws DatabaseException{
		IModelDef hasOneModelDef = getModelDefinition(hasOneDao.getModelName());
		Join join = new Join(hasOneModelDef, getModelDefinition(dao.getModelName()), Join.RIGHT);
		Filter[] defaultFilters = getFilters(join, dao);
		db.selectOne(hasOneModelDef, join, mergeFilters(defaultFilters, filters), 0, 1, null, null, callback);
	}

	public void insert(DAO dao, AsyncDBCallback<DAO> callback)  throws DatabaseException{
		db.insert(dao, getModelDefinition(dao.getModelName()), callback);
	}
	public void update(DAO dao, AsyncDBCallback<DAO> callback) throws DatabaseException{
		db.update(dao, getModelDefinition(dao.getModelName()), getFilters(dao), callback);
	}


	private IModelDef getModelDefinition(Class<?> daoClass) throws DatabaseException {
		return getModelDefinition(daoClass.getSimpleName());
	}

	public IModelDef getModelDefinition(String daoClass) throws DatabaseException{
		try {
			return ModelDefinitionProvider.getDefinition(daoClass);
		} catch (ModelDefinitionNotFoundException e) {
			e.printStackTrace();
			throw new DatabaseException();
		}
	}

	public void createModel(IModelDef model, AsyncDBCallback<Boolean> callback) throws DatabaseException{
		db.createModel(model, callback);
	}

	public void empty(IModelDef model, AsyncDBCallback<Integer> callback) throws DatabaseException{
		db.empty(model, callback);
	}

	public void drop(IModelDef model, AsyncDBCallback<Boolean> callback) throws DatabaseException{
		db.drop(model, callback);
	}

	public void delete(DAO dao, AsyncDBCallback<Integer> callback) throws DatabaseException {
		db.delete(getModelDefinition(dao.getClass()), getFilters(dao), callback);
	}

	public void delete(Class<?> daoClass, Filter[] filters, AsyncDBCallback<Integer> callback) throws DatabaseException{
		db.delete(getModelDefinition(daoClass), filters, callback);
	}

	private Filter[] getFilters(Join join, DAO dao) {

		IModelDef model = join.getFirstModel();
		IModelDef foreignModel = join.getForeignModel();

		String modelName = model.getModelName();//Partner
		String foreignTableName = foreignModel.getModelName(); //Advertiser

		String foreignKey = join.getForeignModel().getForeignKeyColumn(modelName);//Advertiser.partnerid

		String[] primaryAttributes = model.getPrimaryAttributes();
		String[] dataTypes = model.getDataTypes();
		String[] attributes = model.getAttributes();

		
		Filter[] filters = new Filter[primaryAttributes.length];

		for(int i = 0; i < primaryAttributes.length; i++ ){
			if(foreignKey != null){
				Object value = dao.get_Value(primaryAttributes[i]);
				Filter f = new Filter(getDBElementName(foreignModel,foreignTableName)+"."+getDBElementName(foreignModel,foreignKey), Filter.EQUAL, value);
				filters[i] = f;
			}
		}
		return filters;
	}
	
	int indexOf(String[] hayStack, String needle){
		for(int i = 0; i < hayStack.length; i++){
			if(hayStack[i].equals(needle)){
				return i;
			}
		}
		return -1;
	}
	

	private Filter[] getFilters(DAO dao) throws DatabaseException {
		IModelDef model = getModelDefinition(dao.getModelName());
		String[] primaryAttributes = model.getPrimaryAttributes();
		Filter[] filters = new Filter[primaryAttributes.length];
		String[] attributes = model.getAttributes();
		
		for(int i = 0; i < primaryAttributes.length; i++ ){
			System.err.println("attributes: "+Arrays.asList(attributes)+" needle: "+primaryAttributes[i]);
			Object value = dao.get_Value(primaryAttributes[i]);
			Filter f = new Filter(getDBElementName(model, primaryAttributes[i]), Filter.EQUAL, value);
			filters[i] = f;
		}
		return filters;
	}
	
	Object get_Value(DAO dao, String dataType){
		return dataType;
		
	}

	private static String getDBElementName(IModelDef model, String elementName){
		if(model.isCaseSensitive()){
			return "\""+elementName+"\"";
		}else{
			return elementName;
		}
	}

	private Filter[] mergeFilters(Filter[] filters1, Filter[] filters2){
		int filter1Cnt = (filters1 != null ? filters1.length : 0);
		int filter2Cnt = (filters2 != null ? filters2.length : 0);
		int totalFilters = filter1Cnt + filter2Cnt; 
		if(totalFilters == 0){
			return null;
		}
		Filter[] allFilters = new Filter[totalFilters];
		if(filters1 != null){
			for(int i = 0; i < filters1.length; i++){
				allFilters[i] = filters1[i];
			}
		}
		if(filters2!=null){
			for(int j = 0; j < filters2.length; j++){
				allFilters[filters1.length+j] = filters2[j];
			}
		}
		return allFilters;
	}

	private Filter[] getForeignFilters(IModelDef model, DAO dao, IModelDef foreignModel) {
		String[] primaryAttributes = model.getPrimaryAttributes();
		Filter[] filters = new Filter[1];
		String[] dataTypes = model.getDataTypes();
		String[] attributes = model.getAttributes();
		
		Object value = dao.get_Value(primaryAttributes[0]);

		Filter f = new Filter(getDBElementName(foreignModel, foreignModel.getForeignKeyColumn(model.getModelName())), Filter.EQUAL, value);
		filters[0] = f;
		return filters;
	}

	@SuppressWarnings("unused")
	private Filter[] getUniqueFilters(IModelDef model, DAO dao) {
		String[] uniqueAttributes = model.getUniqueAttributes();
		Filter[] filters = new Filter[uniqueAttributes.length];
		String[] dataTypes = model.getDataTypes();
		String[] attributes = model.getAttributes();
		for(int i = 0; i < uniqueAttributes.length; i++ ){
			Filter f = new Filter(getDBElementName(model, uniqueAttributes[i]), Filter.EQUAL, dao.get_Value(uniqueAttributes[i]));
			filters[i] = f;
		}
		return filters;
	}


	public static String toVariableName(String var){
		String char1 = var.substring(0, 1);
		String rest = var.substring(1, var.length());
		char1 = char1.toLowerCase();
		return char1+rest;
	}

	public void close()
	{
		if(db!=null) DBPool.getDBPool().releaseConnection(db);
		else; // return a SOAP error (no database connection)

		db=null;
	}

	public void finalize() throws Throwable
	{
		if(db!=null) try
		{
			Exception e = new Exception("An Entity Manager was not properly closed.  FIX IMMEDIATELY");
			e.printStackTrace();
			if(db.isTransacted())
			{
				e = new Exception("An Entity Manager started a transaction, but did not commit or rollback.  FIX IMMEDIATELY");
				e.printStackTrace();
			}
		} catch(DatabaseException se) { se.printStackTrace(); throw se; }

		super.finalize();
	}

	public void beginTransaction(AsyncDBCallback<Void> callback) throws DatabaseException
	{
		if(db!=null) db.beginTransaction(callback);
		isTransactionBased = true;
	}

	public void commitTransaction(AsyncDBCallback<Void> callback) throws DatabaseException
	{ if(db!=null) db.commitTransaction(callback); 
	isTransactionBased = false;

	}

	public void rollbackTransaction(AsyncDBCallback<Void> callback) throws DatabaseException
	{ 
		if(db!=null) db.rollbackTransaction(callback); 
		isTransactionBased = false;
	}



}
